#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "WindowSystem.h"

#include "Class.h"
#include "String.h"
#include "Error.h"
#include "WindowPort.h"
#include "ClipBoard.h"
#include "DevBitmap.h"
#include "IAC.h"
#include "OrdColl.h"
#include "ClassManager.h"
#include "Env.h"
#include "Look.h"
#include "TextItem.h"
#include "CompVObject.h"
#include "Data.h"
#include "System.h"
#include "Timer.h"
#include "ET_stdio.h"

WindowSystem
	*gWindowSystem;
int
	gDepth= 1,
	gMaxDepth;
bool
	gColor,
	gGreyScale,
	gWinDebug;

//---- RefreshHandler ----------------------------------------------------------

class RefreshHandler : public Timer {
public:
	RefreshHandler(u_long ms) : Timer(ms)
		{ }
	bool Notify();
};

bool RefreshHandler::Notify()
{
	gWindowSystem->Update();
	Reset();
	return TRUE;
}

RefreshHandler *refresh, *refresh2;

//---- ClipboardService --------------------------------------------------------

class ClipboardService : public Service {
public:
	ClipboardService() : Service("clipboard")
		{ }
	bool FetchTypes(int to, OrdCollection*);
	int Dispatch(int from, const char *req, IStream &is, OStream &os);
	Object *GetObject(int to, Class *want);
};

//---- RemoteData --------------------------------------------------------------

class RemoteData : public ObjectData {
public:
	int holder;
public:
	RemoteData(int from);
	bool CheckTypes(OrdCollection *typelist);
	Object *MakeObject(Class *want);
};

//---- ClipboardService --------------------------------------------------------

static ClipboardService *gClipboardService;

bool ClipboardService::FetchTypes(int to, OrdCollection *types)
{
	bool rr= FALSE;

	Iter next(types);
	Class *cl;

	(*os) << types->Size() SP;
	while (cl= (Class*) next())
		(*os) << cl->Name() SP;

	Talk(to, "canconvert");
	(*is) >> rr;
	fprintf(stderr, "FetchTypes: %d\n", rr);
	return rr;
}

Object *ClipboardService::GetObject(int to, Class *want)
{
	Object *op= 0;
	(*os) << want;
	Talk(to, "asobject");
	(*is) >> op;
	return op;
}

int ClipboardService::Dispatch(int from, const char *req, IStream &is, OStream &os)
{
	int rc= 0;
	Class *theclasstype= 0;

	if (strcmp(req, "asobject") == 0) {
		Data *data= gClipBoard->GetType();
		is >> theclasstype;
		os << data->AsObject(theclasstype);

	} else if (strcmp(req, "holder") == 0) {
		gClipBoard->SetData(new RemoteData(from));

	} else if (strcmp(req, "remove") == 0) {
		gClipBoard->SetData(0);

	} else if (strcmp(req, "canconvert") == 0) {
		char name[100];
		int n;
		bool rr= FALSE;
		Data *data= gClipBoard->GetType();
		OrdCollection *typelist= new OrdCollection;
		
		is >> n;
		for (int i= 0; is && i < n; i++) {
			is >> name;
			Class *cl= gClassManager->FindOrLoad(name);
			if (cl)
				typelist->Add(cl);
		}
		if (typelist && data)
			rr= data->CheckTypes(typelist);
		SafeDelete(typelist);
		os << rr;
	} else
		return Service::Dispatch(from, req, is, os);
	return rc;
}

//---- RemoteData --------------------------------------------------------------

RemoteData::RemoteData(int from) : ObjectData((Object*)0)
{
	holder= from;
}

Object *RemoteData::MakeObject(Class *want)
{
	object= gClipboardService->GetObject(holder, want);
	if (object) {
		classtype= object->IsA();
		return object->DeepClone();
	}
	return 0;
}

bool RemoteData::CheckTypes(OrdCollection *typelist)
{
	if (typelist)
		return gClipboardService->FetchTypes(holder, typelist);
	return FALSE;
}

int WindowSystem::DevHaveSelection(Data*)
{
	return gClipboardService->Broadcast("holder");
}

void WindowSystem::DevCheckSelection()
{
}

//---- MenuBorderItem ----------------------------------------------------------

class MenuBorderItem: public CompositeVObject {
	Layout *l;
public:
	MenuBorderItem(VObject *inner, VObject *title= 0)
				: CompositeVObject(cIdNone, inner, title, 0)
		{ l= title ? gLook->PopUpMenuLayout()
				: gLook->PullDownMenuLayout(); }
	~MenuBorderItem()
		{ Remove(At(0));  /* avoid deletion of inner */ }
	void Draw(Rectangle r)
		{ l->Adorn(this, r);  }
	Metric GetMinSize()
		{ return l->GetMinSize(this); }
	void SetOrigin(Point at)
		{ VObject::SetOrigin(at); l->SetOrigin(this, at); }
	void SetExtent(Point e)
		{ VObject::SetExtent(e); l->SetExtent(this, e); }
};

//---- WSInit ------------------------------------------------------------------

WindowSystem *WindowSystem::Load(const char *name, const char *classname)
{
	WindowSystem *ws;
	ws= (WindowSystem*) gClassManager->Load(name, classname);
	if (!ws->Init0()) {
		delete ws;
		ws= 0;
	}
	return ws;
}

void WindowSystem::WSInit()
{
	if (gWindowSystem)
		return;

	InitSystem();
		
	if (! gInMain)
		::Fatal("WSInit", "InitWindowSystem called before ETInit");

	batch= Env::GetValue("WindowSystem.DoubleBuffer", TRUE);

	gMaxDepth= Env::GetValue("WindowSystem.MaxDepth", 32);
	gGreyScale= Env::GetValue("WindowSystem.GreyScale", FALSE);

	gWinDebug= Env::GetValue("WindowSystem.Test", FALSE);

	if (gWindowSystem == 0 && Env::Defined("GLX"))
		gWindowSystem= Load("glxserver", "GLXWinSystem");
	
	if (gWindowSystem == 0 && Env::Defined("DISPLAY"))
		gWindowSystem= Load("xwindow", "XWinSystem");

	if (gWindowSystem == 0 && Env::Defined("WINDOW_PARENT"))
		gWindowSystem= Load("sun", "SunWindowSystem");
	
	if (gWindowSystem == 0) {
		fprintf(stderr, "can't initialize window system\n");
		gSystem->Exit(-1);
	}
	
	gWindowSystem->Init();
	gSystem->RemoveOnExit(gWindowSystem);

	gClipboardService= new ClipboardService;
}

//---- WindowSystem ------------------------------------------------------------

NewMetaImpl(WindowSystem,Object, (TP(allwins), T(lasttime), TB(fullscreen),
			TP(grabport), T(Clicks), T(screenRect), T(batch)));

bool WindowSystem::batch;
u_long WindowSystem::lasttime;
bool WindowSystem::fullscreen;
WindowPort *WindowSystem::grabport;
Rectangle WindowSystem::screenRect(0, 0, 1152, 900);
Token WindowSystem::lastClick;
int WindowSystem::Clicks;
OrdCollection *WindowSystem::allwins= 0;
WindowColorMap *WindowSystem::wcmap= 0;

WindowSystem::WindowSystem()
{
}

bool WindowSystem::Init0()
{
	return FALSE;
}

void WindowSystem::Init()
{
	allwins= new OrdCollection;

	gColor= gDepth > 1;

	gFontManager= MakeFontManager();
	if (gFontManager == 0)
		Fatal("Init", "no font manager");

	if (gFontManager->Init())
		Fatal("Init", "initialization of font manager failed");
		
	gInkManager= new InkManager;
	if (gDepth > 1) {
		gHighlightColor= new_Ink("WindowSystem.HighlightColor",
				new_RGBColor(0, 255, 0)); // light green
	} else
		gHighlightColor= gInkXor;
	MakeLook(Env::GetValue("WindowSystem.Motif", 0));

	int n= Env::GetValue("WindowSystem.UpdateTimeout", 100);
	refresh= new RefreshHandler(n);
	refresh2= new RefreshHandler(1000);
}

WindowSystem::~WindowSystem()
{
	if (refresh) {
		refresh->Remove();
		refresh= 0;
	}
	if (refresh2) {
		refresh2->Remove();
		refresh2= 0;
	}
	if (gWindowSystem == this)
		gWindowSystem= 0;
	SafeDelete(gFontManager);
	SafeDelete(gInkManager);
	SafeDelete(allwins);
	//SafeDelete(wcmap);
	SafeDelete(gClipboardService);
}

WindowPort *WindowSystem::FindWindowWithId(int winid)
{
	if (winid != 0 && winid != -1 && allwins) {
		register WindowPort *wp;
		Iter next(allwins);

		while (wp= (WindowPort*) next())
			if (wp->DevGetId() == winid)
				return wp;
	}
	return 0;
}

VObject *WindowSystem::MakeWindowBorder(WindowType wt, VObject *in,
														char *title, bool, bool)
{
	switch (wt) {
	case eWinPulldownMenu:
		return new MenuBorderItem(in);
	case eWinPopupMenu:
		return new MenuBorderItem(in, new TextItem(title));
	default:
		return in;
	}
}

void WindowSystem::SetWait()
{
	allwins->ForEach(WindowPort,DevSetCursor)(eCrsHourglass);
}

void WindowSystem::ResetWait()
{
	Iter next(allwins);
	WindowPort *wp;

	while (wp= (WindowPort*) next())
		wp->DevSetCursor(wp->GetCursor());
}

void WindowSystem::Update()
{
	allwins->ForEach(WindowPort,DevUpdate)();
}

WindowPort *WindowSystem::MakeWindow(Window*, WindowType, GrCursor)
{
	return 0;
}

void WindowSystem::RemoveWindow(WindowPort *wp)
{
	if (allwins)
		allwins->RemovePtr(wp);
}

void WindowSystem::AddWindow(WindowPort *wp)
{
	if (allwins)
		allwins->Add(wp);
}

FontManager *WindowSystem::MakeFontManager()
{
	return 0;
}

DevBitmap *WindowSystem::MakeDevBitmap(Point, u_short*, u_short, bool, u_short)
{
	return 0;
}

void WindowSystem::graphicDelay(u_int duration)
{
	gSystem->Wait(duration);
}

void WindowSystem::DevBell(long)
{
}

IACImpl *WindowSystem::MakeRpcImpl(IAC *sc)
{
	return new IACImpl(sc);
}

static Rectangle lastdroprect;
static int lastdroptargetid;

int WindowSystem::DragDrop(int code, const Point &globalpos)
{
	WindowPort *wp;
	int droptargetid;

	droptargetid= DevFindWindow(globalpos); // find global
	wp= FindWindowWithId(droptargetid);     // is it local ?
	if (wp) {   // yes, it's a local window
		lastdroptargetid= 0;
		lastdroprect= gRect0;
		return wp->DragDropNotify(code, globalpos, &lastdroprect);
	}

	if (!DevIsEtWindow(droptargetid))       // not an ET++ window
		return 0;
		
	return DevDragDrop(code, droptargetid, globalpos, &lastdroprect);

	/*

	if (droptargetid != lastdroptargetid) {     // not same as last time
		lastdroptargetid= droptargetid;
		lastdroprect= gRect0;
		return DevDragDrop(code, droptargetid, globalpos, &lastdroprect);
	}
	if (lastdroprect.IsEmpty()) {   // same but empty
		return DevDragDrop(code, droptargetid, globalpos, &lastdroprect);
	}
	if (lastdroprect.ContainsPoint(globalpos))
		return 1;

	return 0;
	*/
}

int WindowSystem::DevFindWindow(const Point &globalpos)
{
	if (allwins) {
		Iter next(allwins);
		WindowPort *wp;

		while (wp= (WindowPort*) next())
			if (wp->portrect.ContainsPoint(globalpos))
				return (int)wp;     // use address as id
	}
	return 0;
}

bool WindowSystem::DevIsEtWindow(int winid)
{
	AbstractMethod("DevIsEtWindow");
	return FALSE;
}

int WindowSystem::DevDragDrop(int code, int winid, const Point &globalpos, Rectangle *droprect)
{
	AbstractMethod("DevDragDrop");
	return 0;
}

